// 导入threejs
import * as THREE from "three";

// 根据模型创建随机点集合体
class RandomModelPoints extends THREE.Points {
  constructor(mesh, number = 5000) {
    super();
    this.name = "RandomModelPoints";
    const geometry = mesh.geometry;
    const material = mesh.material;

    this.geometry = this.generatePoints(geometry, material, number);

    // 纹理贴图
    const textureLoader = new THREE.TextureLoader();

    const particlesTexture = textureLoader.load("./texture/particles/1.png");

    // 创建点的材质
    this.material = new THREE.PointsMaterial({
      size: 0.05, // 点的大小
      vertexColors: true, // 顶点颜色
      transparent: true, // 开启透明度
      opacity: 0.8, // 设置透明度
      depthWrite: false, // 设置深度写入
      blending: THREE.AdditiveBlending, // 设置混合模式(叠加)
      sizeAttenuation: true, // 设置距离衰减
      map: particlesTexture, // 纹理贴图
      alphaMap: particlesTexture, // 透明度贴图
      onBeforeCompile: shader => {
        shader.vertexShader = `
          attribute float vSize;
          ${shader.vertexShader}
        `.replace(
          `gl_PointSize = size;`,
          `gl_PointSize = size * vSize;`
        );
        shader.fragmentShader = `
          ${shader.fragmentShader}
        `;
      }
    });
  }
  // 根据模型生成随机点
  generatePoints(geometry, material, numPoints) {
    // 创建新的 BufferGeometry 对象用于存储随机点的几何信息
    const pointsGeometry = new THREE.BufferGeometry();
    // 获取模型的顶点位置属性
    const positionAttribute = geometry.getAttribute("position");
    // 获取模型的纹理坐标属性（如果有）
    const uvAttribute = geometry.getAttribute("uv");
    // 获取模型的索引属性（如果有）
    const indexAttribute = geometry.getIndex();
    // 创建点位置所需要的数据
    const positions = new Float32Array(numPoints * 3);
    const randomPositions = new Float32Array(numPoints * 3);
    // 创建颜色所需要的数据
    const colors = new Float32Array(numPoints * 3);
    // 创建大小所需要的数据
    const sizes = new Float32Array(numPoints);
    // 随机方向
    const randomDirections = new Float32Array(numPoints * 3);
    // 随机速度
    const randomSpeeds = new Float32Array(numPoints);

    // 获取材质的纹理贴图
    const texture = material.map;
    let faceIndex, a, b, c, uvA, uvB, uvC;

    // Cache canvas and context
    let textureData = null;
    let textureWidth = 0,
      textureHeight = 0;

    // 如果有纹理贴图
    if (texture) {
      // 创建临时画布
      const canvas = document.createElement("canvas");
      textureWidth = texture.image.width;
      textureHeight = texture.image.height;
      canvas.width = textureWidth;
      canvas.height = textureHeight;
      // 获取画布上下文
      const context = canvas.getContext("2d");
      // 在画布上绘制纹理图像
      context.drawImage(texture.image, 0, 0, textureWidth, textureHeight);
      // 获取画布的图像数据
      textureData = context.getImageData(
        0,
        0,
        textureWidth,
        textureHeight
      ).data;
    }

    // 循环生成随机点
    for (let i = 0; i < numPoints; i++) {
      // 如果模型有索引属性
      if (indexAttribute) {
        // 随机选择一个三角形面的索引
        faceIndex = Math.floor(Math.random() * (indexAttribute.count / 3)) * 3;
        // 根据索引从顶点位置属性中获取三角形的第一个顶点坐标
        a = new THREE.Vector3().fromBufferAttribute(
          positionAttribute,
          indexAttribute.getX(faceIndex)
        );
        // 根据索引从顶点位置属性中获取三角形的第二个顶点坐标
        b = new THREE.Vector3().fromBufferAttribute(
          positionAttribute,
          indexAttribute.getX(faceIndex + 1)
        );
        // 根据索引从顶点位置属性中获取三角形的第三个顶点坐标
        c = new THREE.Vector3().fromBufferAttribute(
          positionAttribute,
          indexAttribute.getX(faceIndex + 2)
        );
      } else {
        // 如果没有索引属性，随机选择一个三角形
        faceIndex =
          Math.floor(Math.random() * (positionAttribute.count / 3)) * 3;
        a = new THREE.Vector3().fromBufferAttribute(
          positionAttribute,
          faceIndex
        );
        b = new THREE.Vector3().fromBufferAttribute(
          positionAttribute,
          faceIndex + 1
        );
        c = new THREE.Vector3().fromBufferAttribute(
          positionAttribute,
          faceIndex + 2
        );
      }

      // 在三角形内部生成一个随机点
      const randomPoint = this.getRandomPointInTriangle(a, b, c);
      // 将随机点的坐标存储在 positions 数组中
      positions[i * 3] = randomPoint.x;
      positions[i * 3 + 1] = randomPoint.y;
      positions[i * 3 + 2] = randomPoint.z;

      // 生成随机的初始位置
      randomPositions[i * 3] = (Math.random() - 0.5) * 10;
      randomPositions[i * 3 + 1] = (Math.random() - 0.5) * 10;
      randomPositions[i * 3 + 2] = (Math.random() - 0.5) * 10;

      // 生成随机速度
      randomSpeeds[i] = Math.random() * 0.5 + 0.1;
      // 生成随机方向向量并归一化
      let randomDirection = new THREE.Vector3(
        Math.random() * 4 - 2, // X 方向 -1 到 1 之间的随机数
        Math.random() * 4 - 2, // Y 方向 -1 到 1 之间的随机数
        Math.random() * 4 - 2 // Z 方向 -1 到 1 之间的随机数
      ).normalize();
      // 将随机方向向量的分量存储在 randomDirections 数组中
      randomDirections[i * 3] = randomDirection.x;
      randomDirections[i * 3 + 1] = randomDirection.y;
      randomDirections[i * 3 + 2] = randomDirection.z;

      // 生成随机的点大小
      sizes[i] = Math.random() ** 2;

      // 处理颜色
      if (texture && uvAttribute) {
        // 如果有纹理贴图和纹理坐标属性，获取三角形的纹理坐标
        uvA = new THREE.Vector2().fromBufferAttribute(
          uvAttribute,
          indexAttribute ? indexAttribute.getX(faceIndex) : faceIndex
        );
        uvB = new THREE.Vector2().fromBufferAttribute(
          uvAttribute,
          indexAttribute ? indexAttribute.getX(faceIndex + 1) : faceIndex + 1
        );
        uvC = new THREE.Vector2().fromBufferAttribute(
          uvAttribute,
          indexAttribute ? indexAttribute.getX(faceIndex + 2) : faceIndex + 2
        );
        // 在三角形的纹理坐标内部生成一个随机纹理坐标
        const randomUV = this.getRandomPointInTriangle(uvA, uvB, uvC);
        // 从纹理数据中获取对应随机纹理坐标的颜色
        const color = this.getColorFromTexture(
          textureData,
          textureWidth,
          textureHeight,
          randomUV
        );
        // 将颜色分量存储在 colors 数组中
        colors[i * 3] = color.r;
        colors[i * 3 + 1] = color.g;
        colors[i * 3 + 2] = color.b;
      } else {
        // 如果没有纹理贴图或纹理坐标属性，生成默认随机颜色
        colors[i * 3] = Math.random() * 0.5;
        colors[i * 3 + 1] = Math.random() * 0.5;
        colors[i * 3 + 2] = 0.5 + Math.random() * 0.5;
      }
    }

    // 将随机点的位置属性设置为 pointsGeometry 的属性
    pointsGeometry.setAttribute(
      "position",
      new THREE.BufferAttribute(positions, 3)
    );
    // 将随机点的颜色属性设置为 pointsGeometry 的属性
    pointsGeometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
    // 将随机点的大小属性设置为 pointsGeometry 的属性
    pointsGeometry.setAttribute("vSize", new THREE.BufferAttribute(sizes, 1));
    // 将随机点的随机位置属性设置为 pointsGeometry 的属性
    pointsGeometry.setAttribute(
      "randomPosition",
      new THREE.BufferAttribute(randomPositions, 3)
    );
    // 将随机点的随机方向属性设置为 pointsGeometry 的属性
    pointsGeometry.setAttribute(
      "randomDirection",
      new THREE.BufferAttribute(randomDirections, 3)
    );
    // 将随机点的随机速度属性设置为 pointsGeometry 的属性
    pointsGeometry.setAttribute(
      "randomSpeed",
      new THREE.BufferAttribute(randomSpeeds, 1)
    );

    // 返回生成的点的几何信息
    return pointsGeometry;
  }
  // 从纹理数据中获取颜色
  getColorFromTexture(textureData, width, height, uv) {
    // 计算纹理坐标在纹理中的实际位置
    const u = uv.x % 1;
    const v = uv.y % 1;

    // 根据纹理坐标计算在纹理中的像素坐标
    const x = Math.floor(u * width);
    const y = Math.floor(v * height);

    // 对像素坐标进行边界检查
    const clampedX = Math.max(0, Math.min(x, width - 1));
    const clampedY = Math.max(0, Math.min(y, height - 1));

    // 计算在纹理数据中的偏移量
    const offset = (clampedY * width + clampedX) * 4;
    // 提取纹理数据中的红、绿、蓝三个通道的值并归一化
    const r = textureData[offset] / 255;
    const g = textureData[offset + 1] / 255;
    const b = textureData[offset + 2] / 255;

    // 返回颜色对象
    return new THREE.Color(r, g, b);
  }
  // 在三角形内部生成随机点
  getRandomPointInTriangle(a, b, c) {
    // 生成两个随机数
    const r1 = Math.random();
    const r2 = Math.random();
    const sqrtR1 = Math.sqrt(r1);

    // 计算三角形内随机点的坐标
    const x = (1 - sqrtR1) * a.x + sqrtR1 * (1 - r2) * b.x + sqrtR1 * r2 * c.x;
    const y = (1 - sqrtR1) * a.y + sqrtR1 * (1 - r2) * b.y + sqrtR1 * r2 * c.y;
    const z = (1 - sqrtR1) * a.z + sqrtR1 * (1 - r2) * b.z + sqrtR1 * r2 * c.z;

    // 返回随机点的坐标
    return new THREE.Vector3(x, y, z);
  }
}

export default RandomModelPoints;
